// -*-c++-*-
// vim: set ft=cpp:

/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#pragma once

#include <iomanip> // IWYU pragma: export
#if __cplusplus < 201402L || defined(_MSVC_LANG) && _MSVC_LANG < 201402L
#  include <ios>
#  include <iostream>
#  include <sstream>
#  include <string>
#  include <type_traits>
#endif
#if __cplusplus < 201703L || defined(_MSVC_LANG) && _MSVC_LANG < 201703L
#  include <cm/string_view>
#endif

namespace cm {

#if __cplusplus >= 201402L || defined(_MSVC_LANG) && _MSVC_LANG >= 201402L

using std::quoted;

#  if __cplusplus < 201703L || defined(_MSVC_LANG) && _MSVC_LANG < 201703L

inline auto quoted(cm::string_view str, char delim = '"', char escape = '\\')
{
  return std::quoted(static_cast<std::string>(str), delim, escape);
}

#  endif

#else

namespace internals {

// Struct for delimited strings.
template <typename String, typename Char>
struct quoted_string
{
  static_assert(std::is_reference<String>::value ||
                  std::is_pointer<String>::value,
                "String type must be pointer or reference");

  quoted_string(String str, Char del, Char esc)
    : string_(str)
    , delim_{ del }
    , escape_{ esc }
  {
  }

  quoted_string& operator=(quoted_string&) = delete;

  String string_;
  Char delim_;
  Char escape_;
};

template <>
struct quoted_string<cm::string_view, char>
{
  quoted_string(cm::string_view str, char del, char esc)
    : string_(str)
    , delim_{ del }
    , escape_{ esc }
  {
  }

  quoted_string& operator=(quoted_string&) = delete;

  cm::string_view string_;
  char delim_;
  char escape_;
};

template <typename Char, typename Traits>
std::basic_ostream<Char, Traits>& operator<<(
  std::basic_ostream<Char, Traits>& os,
  const quoted_string<const Char*, Char>& str)
{
  std::basic_ostringstream<Char, Traits> ostr;
  ostr << str.delim_;
  for (const Char* c = str.string_; *c; ++c) {
    if (*c == str.delim_ || *c == str.escape_)
      ostr << str.escape_;
    ostr << *c;
  }
  ostr << str.delim_;

  return os << ostr.str();
}

template <typename Char, typename Traits, typename String>
std::basic_ostream<Char, Traits>& operator<<(
  std::basic_ostream<Char, Traits>& os, const quoted_string<String, Char>& str)
{
  std::basic_ostringstream<Char, Traits> ostr;
  ostr << str.delim_;
  for (auto c : str.string_) {
    if (c == str.delim_ || c == str.escape_)
      ostr << str.escape_;
    ostr << c;
  }
  ostr << str.delim_;

  return os << ostr.str();
}

template <typename Char, typename Traits, typename Alloc>
std::basic_istream<Char, Traits>& operator>>(
  std::basic_istream<Char, Traits>& is,
  const quoted_string<std::basic_string<Char, Traits, Alloc>&, Char>& str)
{
  Char c;
  is >> c;
  if (!is.good())
    return is;
  if (c != str.delim_) {
    is.unget();
    is >> str.string_;
    return is;
  }
  str.string_.clear();
  std::ios_base::fmtflags flags =
    is.flags(is.flags() & ~std::ios_base::skipws);
  do {
    is >> c;
    if (!is.good())
      break;
    if (c == str.escape_) {
      is >> c;
      if (!is.good())
        break;
    } else if (c == str.delim_)
      break;
    str.string_ += c;
  } while (true);
  is.setf(flags);

  return is;
}
}

template <typename Char>
inline internals::quoted_string<const Char*, Char> quoted(
  const Char* str, Char delim = Char('"'), Char escape = Char('\\'))
{
  return internals::quoted_string<const Char*, Char>(str, delim, escape);
}

template <typename Char, typename Traits, typename Alloc>
inline internals::quoted_string<const std::basic_string<Char, Traits, Alloc>&,
                                Char>
quoted(const std::basic_string<Char, Traits, Alloc>& str,
       Char delim = Char('"'), Char escape = Char('\\'))
{
  return internals::quoted_string<
    const std::basic_string<Char, Traits, Alloc>&, Char>(str, delim, escape);
}

template <typename Char, typename Traits, typename Alloc>
inline internals::quoted_string<std::basic_string<Char, Traits, Alloc>&, Char>
quoted(std::basic_string<Char, Traits, Alloc>& str, Char delim = Char('"'),
       Char escape = Char('\\'))
{
  return internals::quoted_string<std::basic_string<Char, Traits, Alloc>&,
                                  Char>(str, delim, escape);
}

inline internals::quoted_string<cm::string_view, char> quoted(
  cm::string_view str, char delim = '"', char escape = '\\')
{
  return internals::quoted_string<cm::string_view, char>(str, delim, escape);
}

#endif

} // namespace cm
